﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.IO;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;

using Microsoft.Practices.Unity;
using Unity.AutoRegistration;

using ObjectEditor.UserControls;

namespace ObjectEditor
{
    using ObjectEditor.Display3D;
    using ObjectEditor.Editor;
    using System.Windows.Media.Media3D;
    using HelixToolkit.Wpf;

    public sealed class MainWindowViewModel : ViewModel, IDisposable
    {
        //private CuttingPlaneGroup _cutter = new CuttingPlaneGroup()
        //{
        //    IsEnabled = true
        //};
        public event EventHandler<CameraChangeEventArgs> ChangeCameraRequested;

        private DelegateCommand _hideLayerCommand;
        private DelegateCommand _showLayerCommand;

        private Plane3D _maxXPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(-1, 0, 0));
        private Plane3D _minXPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 0));
        private Plane3D _maxYPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 0));
        private Plane3D _minYPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 0));
        private Plane3D _maxZPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 0));
        private Plane3D _minZPlane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 0));

        private bool _enableMaxXCut = false;
        private bool _enableMinXCut = false;
        private bool _enableMaxYCut = false;
        private bool _enableMinYCut = false;
        private bool _enableMaxZCut = false;
        private bool _enableMinZCut = false;
        private double _maxXCut, _minXCut, _maxYCut, _minYCut, _maxZCut, _minZCut;

        private double _cameraLookAtDistance;
        private double _cameraLookAtAnimationTimeMs;
        private bool _allowCameraLookAt;

        private object _selectedObject;
        private IEnumerable<INotifyPropertyChanged> _allObjectsSelected;

        private List<string> _loadedModuleNames = new List<string>();
        private IUnityContainer _diContainer = new UnityContainer();

        private ObservableCollection<IDynamicRibbonTab> _ribbonItems =
            new ObservableCollection<IDynamicRibbonTab>();

        //private ObservableCollection<object> _cutterModels = new ObservableCollection<object>();
        //this stores all the models, as opposed to the world models which only hold the models actually being displayed
        private ObservableCollection<object> _allModels = new ObservableCollection<object>();

        private ObservableCollection<object> _worldModels =
            new ObservableCollection<object>();
        private Dictionary<IWorldModel3DProvider, ModelVisual3D> _modelMappings = new Dictionary<IWorldModel3DProvider, ModelVisual3D>();

        public MainWindowViewModel()
        {
            if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
            {
                SelectedObject = new { Id = 0, FirstName = "John", LastName = "Smith" };
            }

            CameraLookAtAnimationTimeMs = 1000;
            CameraLookAtDistance = 500;
            AllowCameraLookAt = true;

            //_cutter.CuttingPlanes.Add(_maxXPlane);
            //_cutter.CuttingPlanes.Add(_minXPlane);
            //_cutter.CuttingPlanes.Add(_maxYPlane);
            //_cutter.CuttingPlanes.Add(_minYPlane);
            //_cutter.CuttingPlanes.Add(_maxZPlane);
            //_cutter.CuttingPlanes.Add(_minZPlane);

            //loads all assemblies
            LoadPluginAssemblies(new string[]{ "./Plugins" });

            //setup the registration of the ioc container
            InjectionContainerRegistration();

            //initialize all modules
            InitializeModules();

            //hookup to everything registered
            SubscribeToObjectEditors();
            SubscribeToWorldViewItems();
            SubscribeToMessengers();

            //create all user controls
            CreateRibbonTabs();            
        }

        private void InitializeModules()
        {
            var modules = _diContainer.ResolveAll<Configuration.IModuleConfiguration>().ToList();

            //first pass - all modules initialize
            foreach (var module in modules.ToArray())
            {
                try
                {
                    module.Initialize(_diContainer);
                }
                catch (Exception e)
                {
                    var msg = "Failed to initialize module configuration:" + module.ToString() + "," + e.Message;
                    Debug.WriteLine(msg);

                    //remove it from the modules to configure since it can cause more problems
                    modules.Remove(module);
                }
            }

            //last pass - finalize all configurations
            foreach (var module in modules.ToArray())
            {
                try
                {
                    module.Finalize(_diContainer);
                }
                catch (Exception e)
                {
                    var msg = "Failed to finalize module configuration:" + module.ToString() + "," + e.Message;
                    Debug.WriteLine(msg);
                }
            }
        }

        private void SubscribeToMessengers()
        {
            foreach (var messenger in _diContainer.ResolveAll<IMessenger>())
            {
                messenger.MessageSent += messenger_MessageSent;
            }
        }

        void messenger_MessageSent(object sender, MessageEventArgs e)
        {
            foreach (var messenger in _diContainer.ResolveAll<IMessenger>().Where( x => x != sender ))
            {
                messenger.MessageAvailable(sender, e);
            }
        }

        private void SubscribeToObjectEditors()
        {
            foreach (var editor in _diContainer.ResolveAll<IObjectEditor>())
            {
                editor.ObjectSelected += editor_ObjectSelected;
            }
        }

        void editor_ObjectSelected(object sender, ObjectSelectedEventArgs args)
        {
            SelectedObject = args.Object;
            AllSelectedObjects = args.AllObjectsSelected;
        }

        private void SubscribeToWorldViewItems()
        {
            foreach (var model in _diContainer.ResolveAll<IWorldModel3DProvider>())
            {
                model.ModelChanged += model_ModelChanged;
                model.RequestCameraChange += model_RequestCameraChange;
            }
        }

        void model_RequestCameraChange(object sender, CameraChangeEventArgs args)
        {
            if (!AllowCameraLookAt) args.LookAtPosition = null;
            else
            {
                if (!args.LookAtAnimationTimeMs.HasValue)
                {
                    args.LookAtAnimationTimeMs = CameraLookAtAnimationTimeMs;
                }

                if (!args.LookAtDistance.HasValue)
                {
                    args.LookAtDistance = CameraLookAtDistance;
                }
            }

            var e = ChangeCameraRequested;
            if (e != null)
            {
                e(this, args);
            }
        }
        
        private void UpdateCuts()
        {
            var constraints = new ViewConstraints();
            if (_enableMaxXCut)
            {
                constraints.MaxX = MaxXCut;
            }

            if (_enableMinXCut)
            {
                constraints.MinX = MinXCut;
            }

            if (_enableMaxYCut)
            {
                constraints.MaxY = MaxYCut;
            }

            if (_enableMinYCut)
            {
                constraints.MinY = MinYCut;
            }

            if (_enableMaxZCut)
            {
                constraints.MaxZ = MaxZCut;
            }

            if (_enableMinZCut)
            {
                constraints.MinZ = MinZCut;
            }


            foreach (var model in _diContainer.ResolveAll<IWorldModel3DProvider>())
            {
                try
                {
                    model.ConstrainView(constraints);
                }
                catch (Exception e)
                {
                    System.Windows.MessageBox.Show("Failed to update view constraints on:" + model.ToString() + "," + e.Message);
                }
            }
            /*
            _cutter.CuttingPlanes.RemoveAll(x => true);

            if (_enableMaxXCut)
            {
                _maxXPlane.Normal = new Vector3D(-1, 0, 0);
                _maxXPlane.Position = new Point3D(_maxXCut, 0, 0);
                _cutter.CuttingPlanes.Add(_maxXPlane);
            }

            if (_enableMinXCut)
            {
                _minXPlane.Normal = new Vector3D(1, 0, 0);
                _minXPlane.Position = new Point3D(_minXCut, 0, 0);
                _cutter.CuttingPlanes.Add(_minXPlane);
            }

            if (_enableMaxYCut)
            {
                _maxYPlane.Normal = new Vector3D(0, -1, 0);
                _maxYPlane.Position = new Point3D(0, _maxYCut, 0);
                _cutter.CuttingPlanes.Add(_maxYPlane);
            }
            if (_enableMinYCut)
            {
                _minYPlane.Normal = new Vector3D(0, 1, 0);
                _minYPlane.Position = new Point3D(0, _minYCut, 0);
                _cutter.CuttingPlanes.Add(_minYPlane);
            }

            if (_enableMaxZCut)
            {
                _maxZPlane.Normal = new Vector3D(0, 0, -1);
                _maxZPlane.Position = new Point3D(0, 0, _maxZCut);
                _cutter.CuttingPlanes.Add(_maxZPlane);
            }
            if (_enableMinZCut)
            {
                _minZPlane.Normal = new Vector3D(0, 0, 1);
                _minZPlane.Position = new Point3D(0, 0, _minZCut);
                _cutter.CuttingPlanes.Add(_minZPlane);
            }

            _cutter.IsEnabled = false;
            _cutter.IsEnabled = _enableMaxXCut | _enableMaxYCut | _enableMaxZCut | _enableMinXCut | _enableMinYCut | _enableMinZCut;
            */

            OnPropertyChanged("WorldViewItems");
        }

        public DelegateCommand ShowLayerCommand
        {
            get
            {
                if (_showLayerCommand == null)
                {
                    _showLayerCommand = new DelegateCommand(x =>
                    {
                        ShowLayer(x);
                    }, y =>
                    {
                        return true;
                    });
                }

                return _showLayerCommand;
            }
        }

        public DelegateCommand HideLayerCommand
        {
            get
            {
                if (_hideLayerCommand == null)
                {
                    _hideLayerCommand = new DelegateCommand(x =>
                    {
                        HideLayer(x);
                    }, y =>
                    {
                        return true;
                    });
                }
                return _hideLayerCommand;
            }
        }

        public double MaxXCut
        {
            get { return _maxXCut; }
            set
            {
                _maxXCut = value;
                UpdateCuts();
                OnPropertyChanged("MaxXCut");
            }
        }

        public double MinXCut
        {
            get { return _minXCut; }
            set
            {
                _minXCut = value;
                UpdateCuts();
                OnPropertyChanged("MinXCut");
            }
        }

        public double MaxYCut
        {
            get { return _maxYCut; }
            set
            {
                _maxYCut = value;
                UpdateCuts();
                OnPropertyChanged("MaxYCut");
            }
        }

        public double MinYCut
        {
            get { return _minYCut; }
            set
            {
                _minYCut = value;
                UpdateCuts();
                OnPropertyChanged("MinYCut");
            }
        }

        public double MaxZCut
        {
            get { return _maxZCut; }
            set
            {
                _maxZCut = value;
                UpdateCuts();
                OnPropertyChanged("MaxZCut");
            }
        }

        public double MinZCut
        {
            get { return _minZCut; }
            set
            {
                _minZCut = value;
                UpdateCuts();
                OnPropertyChanged("MinZCut");
            }
        }

        public bool EnableMaxXCut
        {
            get { return _enableMaxXCut; }
            set
            {
                _enableMaxXCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMaxXCut");
            }
        }

        public bool EnableMinXCut
        {
            get { return _enableMinXCut; }
            set
            {
                _enableMinXCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMinXCut");
            }
        }

        public bool EnableMaxYCut
        {
            get { return _enableMaxYCut; }
            set
            {
                _enableMaxYCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMaxYCut");
            }
        }

        public bool EnableMinYCut
        {
            get { return _enableMinYCut; }
            set
            {
                _enableMinYCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMinYCut");
            }
        }

        public bool EnableMaxZCut
        {
            get { return _enableMaxZCut; }
            set
            {
                _enableMaxZCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMaxZCut");
            }
        }

        public bool EnableMinZCut
        {
            get { return _enableMinZCut; }
            set
            {
                _enableMinZCut = value;
                UpdateCuts();
                OnPropertyChanged("EnableMinZCut");
            }
        }

        void model_ModelChanged(object sender, ModelChangedEventArgs args)
        {
            if (WorldViewItems.Count == 0)
            {
                WorldViewItems.Add(new HelixToolkit.Wpf.DefaultLights());
                WorldViewItems.Add(new CoordinateSystemVisual3D());
                //WorldViewItems.Add(_cutter);
            }            

            var obj = sender as IWorldModel3DProvider;
            if (obj != null && obj.Model3D != null)
            {
                //keep a mapping of the models so old ones can be removed if the provider changes models                
                if (_modelMappings.ContainsKey(obj))
                {
                    //_cutter.Children.Remove(_modelMappings[obj]);
                    WorldViewItems.Remove(_modelMappings[obj]);
                    _allModels.Remove(_modelMappings[obj]);
                }
                //_cutter.Children.Add(obj.Model3D);
                WorldViewItems.Add(obj.Model3D);
                _modelMappings[obj] = obj.Model3D;
                _allModels.Add(obj.Model3D);
            }

            //_cutterModels.Clear();
            //foreach (var item in _cutter.Children)
            //{
            //    _cutterModels.Add(item);
            //}

            OnPropertyChanged("WorldViewItems");            
        }

        public IEnumerable<object> WorldLayers
        {
            get { return this._allModels; }
        }
        
        public void HideLayer(object value)
        {
            Visual3D v3d = value as Visual3D;
            if (v3d == null) return;

            WorldViewItems.Remove(value);
            //_cutter.Children.Remove(v3d);            
        }

        public void ShowLayer(object value)
        {
            Visual3D v3d = value as Visual3D;
            if (v3d == null) return;

            if (!WorldViewItems.Contains(value))
            {
                WorldViewItems.Add(value);
            }
            //if (!_cutter.Children.Contains(v3d))
            //{
            //    _cutter.Children.Add(v3d);
            //}
        }

        public IEnumerable<IDynamicTab> GetDynamicTabs()
        {
            return _diContainer.ResolveAll<IDynamicTab>();
        }

        private void CreateRibbonTabs()
        {
            var tabs = _diContainer.ResolveAll<IDynamicRibbonTab>();

            foreach (var tab in tabs.OrderBy(x => x.ToString()))
            {
                RibbonItems.Add(tab);
            }
        }

        private void LoadPluginAssemblies(IEnumerable<string> directories)
        {
            DirectoryCatalog dirCatalog;
            foreach (var dir in directories)
            {
                if (Directory.Exists(dir))
                {
                    dirCatalog = new DirectoryCatalog(dir);
                    _loadedModuleNames.AddRange(dirCatalog.LoadedFiles);
                }
            }
        }

        private void InjectionContainerRegistration()
        {            
            _diContainer.ConfigureAutoRegistration()
                .ExcludeSystemAssemblies()

                .Include( x => x.Implements<IDynamicRibbonTab>(), Then.Register().WithTypeName().UsingSingletonMode() )
                .Include( x => x.Implements<Configuration.IModuleConfiguration>(), Then.Register().WithTypeName().UsingSingletonMode() ) 
                .Include( x => x.Implements<IDynamicTab>(), Then.Register().WithTypeName().UsingSingletonMode() )      

                .ApplyAutoRegistration();
        }

        public void World3DClick(Point3D point,AppInformation info)
        {
            var models = WorldViewItems.Intersect(_allModels);

            foreach (var model in _diContainer.ResolveAll<IWorldModel3DProvider>())            
            {
                //dont sender click event to model
                //if it is being hidden and it exists in the all models collection                
                if ( !(models.Contains(model.Model3D) || !_allModels.Contains(model.Model3D)) ) continue;

                try
                {
                    model.UserClicked(point, info);
                }
                catch (Exception e)
                {
                    System.Windows.MessageBox.Show("World3DClick error from: " + model.ToString() + "," + e.Message);
                }
            } 
        }

        public ObservableCollection<IDynamicRibbonTab> RibbonItems
        {
            get { return _ribbonItems; }
            private set
            {
                _ribbonItems = value;
                OnPropertyChanged("RibbonItems");
            }
        }

        public ObservableCollection<object> WorldViewItems
        {
            get { return _worldModels; }
            set
            {
                _worldModels = value;
                OnPropertyChanged("WorldViewItems");
            }
        }

        public bool AllowCameraLookAt
        {
            get { return _allowCameraLookAt; }
            set
            {
                _allowCameraLookAt = value;
                OnPropertyChanged("AllowCameraLookAt");
            }
        }

        public double CameraLookAtDistance
        {
            get { return _cameraLookAtDistance; }
            set
            {
                _cameraLookAtDistance = value;
                OnPropertyChanged("CameraLookAtDistance");
            }
        }

        public double CameraLookAtAnimationTimeMs
        {
            get { return _cameraLookAtAnimationTimeMs; }
            set
            {
                _cameraLookAtAnimationTimeMs = value;
                OnPropertyChanged("CameraLookAtAnimationTimeMs");
            }
        }

        public object SelectedObject
        {
            get
            {
                return _selectedObject;
            }

            set
            {
                _selectedObject = value;
                OnPropertyChanged("SelectedObject");
            }
        }

        public IEnumerable<INotifyPropertyChanged> AllSelectedObjects
        {
            get { return _allObjectsSelected; }
            set
            {
                _allObjectsSelected = value;
                OnPropertyChanged("AllSelectedObjects");
                OnPropertyChanged("AllSelectedObjectsCount");
            }
        }

        public int AllSelectedObjectsCount
        {
            get { return _allObjectsSelected != null ? _allObjectsSelected.Count() : 0; }
        }

        public void Dispose()
        {
            //var modules = _diContainer.ResolveAll<Configuration.IModuleConfiguration>().ToList();
            //foreach (var module in modules)
            //{
            //    module.Dispose();
            //}

            if (_diContainer != null)
            {
                _diContainer.Dispose();
            }
        }
    }

    public class AppInformation
    {
        public object SelectedRibbonTab
        {
            get;
            set;
        }

        public Func<Point3D, double, bool> World3DSelectionBox
        {
            get;
            set;
        }
    }
}
